import './Blackboard.css'
import React from 'react';
import { ImageLoader } from '../../service/Loader';
import backgroundJPG from '../../resource/background.jpg';
import gclefPNG from '../../resource/gclef.png';
import noteUpPNG from '../../resource/note_up.png';
import noteDownPNG from '../../resource/note_down.png';
import yesPNG from '../../resource/yes.png';
import noPNG from '../../resource/no.png';
import { Point2D, Rect2D } from '../../service/Math';
import { Clef, NoteSymbol, staffTable } from '../../service/staff';

interface BlackboardProps {

    noteSymbol: NoteSymbol;

    clef: Clef;

    correctNumber: number;

    errorNumber: number;

}

export class Blackboard extends React.Component<BlackboardProps, {}> {


    private readonly preloadImageSrcList = [backgroundJPG, gclefPNG, noteUpPNG, noteDownPNG, yesPNG, noPNG];

    private imageMap: Map<string, HTMLImageElement>;

    private pianoBlackboardCanvas: PianoBlackboardCanvas;


    componentDidUpdate = () => {
        
        this.pianoBlackboardCanvas?.nextRound(this.props.noteSymbol, this.props.clef, this.props.correctNumber, this.props.errorNumber);
    }

    componentDidMount = () => {

        let canvas: HTMLCanvasElement = document.getElementById('piano-blackboard') as HTMLCanvasElement;

        ImageLoader.loadAll(this.preloadImageSrcList).then(imageMap => {
            this.imageMap = new Map(imageMap);

            this.pianoBlackboardCanvas = new PianoBlackboardCanvas(canvas,
                this.imageMap.get(backgroundJPG), this.imageMap.get(gclefPNG), this.imageMap.get(noteUpPNG), this.imageMap.get(noteDownPNG),
                this.imageMap.get(yesPNG), this.imageMap.get(noPNG));

            this.pianoBlackboardCanvas.nextRound(this.props.noteSymbol, this.props.clef, this.props.correctNumber, this.props.errorNumber);
        });
    }

    render = () => {

        return <canvas id="piano-blackboard" width={window.screen.availWidth} height={window.screen.availHeight}></canvas>;
    }
}

export class PianoBlackboardCanvas {

    private readonly context2D: CanvasRenderingContext2D;

    // the left top corner of the piano staff
    private readonly staffPoint: Point2D;

    // the staff line width
    private readonly staffLineWidth: number;

    // the line height
    private readonly staffLineHeight = 1;

    // the distance between two lines
    private readonly staffDistance: number;

    // the staff table, saves the Y-pixel of each note 
    private readonly noteLocationTable: Map<string, number> = new Map();

    // the left top corner of the clef symbol
    private readonly gClefSymbolRect: Rect2D;

    // the height of the note symbol
    private readonly noteHeight: number;

    // the width of the note symbol
    private readonly noteWidth: number;

    // the x of the note, which is not changed
    private readonly noteX: number;

    // correct and error score point location and size
    private readonly correctScoreRect: Rect2D;

    private readonly errorScoreRect: Rect2D;


    constructor(private canvas: HTMLCanvasElement, private backgroundImage: HTMLImageElement, private gClefImage: HTMLImageElement, private noteUpImage: HTMLImageElement, private noteDownImage: HTMLImageElement, private yesImage: HTMLImageElement, private noImage: HTMLImageElement) {

        this.context2D = canvas.getContext('2d');

        // calcuate all the key point location
        const marginLeft = this.canvas.width / 25;


        // piano staff, five lines, the left top of the first line
        this.staffPoint = {
            x: marginLeft,
            y: this.canvas.height * 0.3
        }

        this.staffLineWidth = this.canvas.width - 2 * marginLeft;
        this.staffDistance = this.canvas.height / 25;


        // G clef symbol
        const gClefSymbolHeight = this.staffDistance * 6;
        this.gClefSymbolRect = {
            x: 2 * marginLeft,
            y: this.staffPoint.y - this.staffDistance,
            height: gClefSymbolHeight,
            width: gClefSymbolHeight * this.gClefImage.width / this.gClefImage.height
        };

        // staff table
        staffTable.forEach(note => this.noteLocationTable.set(note.note, this.staffDistance * 4 + this.staffPoint.y - this.staffDistance * note.offset / 2));

        // note symbol height
        this.noteHeight = 4 * this.staffDistance;

        this.noteWidth = this.noteHeight * this.noteUpImage.width / this.noteUpImage.height;

        this.noteX = this.canvas.width * 0.6;

        // correct and error score
        this.correctScoreRect = {
            x: this.canvas.width * 0.1,
            y: this.canvas.height * 0.05,
            width: this.canvas.width * 0.15,
            height: this.canvas.width * 0.15
        }

        this.errorScoreRect = {
            x: this.canvas.width * 0.55,
            y: this.canvas.height * 0.05,
            width: this.canvas.width * 0.15,
            height: this.canvas.width * 0.15
        }

    }



    /**
     * paint every round
     * 
     * @param noteSymbol 
     * @param clef 
     * @param correctNumber 
     * @param errorNumber 
     */
    public nextRound(noteSymbol: NoteSymbol, clef: Clef, correctNumber: number, errorNumber: number): void {

        // draw backrgound, look like a blackboard
        this.context2D.drawImage(this.backgroundImage, 0, 0, this.backgroundImage.width, this.backgroundImage.height, 0, 0, this.canvas.width, this.canvas.height);

        // draw staff
        this.paintStaff();

        // draw elements
        this.paintClef(clef);

        this.paintNote(noteSymbol);

        this.paintScore(correctNumber, errorNumber);
    }

    /**
     * Paint the piano staff
     */
    public paintStaff(): void {
        this.context2D.strokeStyle = 'rgb(255,255,255)';
        this.context2D.lineWidth = this.staffLineHeight;

        for (let i = 0; i < 5; i++) {

            const y = this.staffPoint.y + i * this.staffDistance;

            this.context2D.beginPath();
            this.context2D.moveTo(this.staffPoint.x, y);
            this.context2D.lineTo(this.staffPoint.x + this.staffLineWidth, y);
            this.context2D.closePath();
            this.context2D.stroke();
        }
    }

    public paintClef(clef: Clef): void {

        switch (clef) {
            case 'G':
                this.context2D.drawImage(this.gClefImage, this.gClefSymbolRect.x, this.gClefSymbolRect.y, this.gClefSymbolRect.width, this.gClefSymbolRect.height);
                break;

            case 'F': break;

            default: break;
        }
    }

    public paintNote(noteSymbol: NoteSymbol): void {


        // 五线谱第三根线以及之上的，尾巴向下，反之，音符尾巴向上
        if (noteSymbol.offset >= 4) {
            this.context2D.drawImage(this.noteDownImage, this.noteX, this.noteLocationTable.get(noteSymbol.note) - this.staffDistance * 0.5, this.noteWidth, this.noteHeight);
        } else {
            this.context2D.drawImage(this.noteUpImage, this.noteX, this.noteLocationTable.get(noteSymbol.note) - this.staffDistance * 3.5, this.noteWidth, this.noteHeight);
        }


        // 超过五线谱部分需要加线
        if (noteSymbol.offset >= 10) {
            for (let i = 1; i <= Math.floor((noteSymbol.offset - 8) / 2); i++) {
                const y = this.staffPoint.y - i * this.staffDistance;
                const x0 = this.noteX - this.noteWidth * 0.3;
                const x1 = this.noteX + this.noteWidth * 1.3;
                this.context2D.beginPath();
                this.context2D.moveTo(x0, y);
                this.context2D.lineTo(x1, y);
                this.context2D.closePath();
                this.context2D.stroke();
            }
        }

        if (noteSymbol.offset <= -2) {
            for (let i = 1; i <= Math.floor(-noteSymbol.offset / 2); i++) {
                const y = this.staffPoint.y + (i + 4) * this.staffDistance;
                const x0 = this.noteX - this.noteWidth * 0.3;
                const x1 = this.noteX + this.noteWidth * 1.3;
                this.context2D.beginPath();
                this.context2D.moveTo(x0, y);
                this.context2D.lineTo(x1, y);
                this.context2D.closePath();
                this.context2D.stroke();
            }
        }
    }

    public paintScore(correctNumber: number, errorNumber: number) {
        this.context2D.drawImage(this.yesImage, this.correctScoreRect.x, this.correctScoreRect.y, this.correctScoreRect.width, this.correctScoreRect.height);
        this.context2D.drawImage(this.noImage, this.errorScoreRect.x, this.errorScoreRect.y, this.errorScoreRect.width, this.errorScoreRect.height);

        this.context2D.font = `${this.correctScoreRect.width * 0.8}px STXingkai`;
        this.context2D.textBaseline = "top";
        this.context2D.fillStyle = "white"
        this.context2D.fillText(String(correctNumber), this.correctScoreRect.x + this.correctScoreRect.width * 1.2, this.correctScoreRect.y * 1.2);
        this.context2D.fillText(String(errorNumber), this.errorScoreRect.x + this.errorScoreRect.width * 1.2, this.errorScoreRect.y * 1.2);
    }
}


